#include "aoi.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <lua.h>
#include <lauxlib.h>

struct alloc_cookie {
	int count;
	int max;
	int current;
};

struct alloc_cookie cookie = { 0,0,0 };

typedef struct OBJECT {
	uint64_t id;
	float pos[3];
	float v[3];
	char mode[4];
}OBJECT;

struct aoi_space_ud{
	struct aoi_space *space;
	int maxmarker;
	lua_State *L;
};

static void *
my_alloc(void * ud, void *ptr, size_t sz) {
	struct alloc_cookie * cookie = ud;
	if (ptr == NULL) {
		void *p = malloc(sz);
		++ cookie->count;
		cookie->current += sz;
		if (cookie->max < cookie->current) {
			cookie->max = cookie->current;
		}
		// printf("%p + %u\n",p, sz);
		return p;
	}
	-- cookie->count;
	cookie->current -= sz;
	// printf("%p - %u \n",ptr, sz);
	free(ptr);
	return NULL;
}

static void 
message(void *ptr, uint64_t watcher, uint64_t marker) {
	struct aoi_space_ud *ud = (struct aoi_space_ud *)ptr;
	lua_State *L=ud->L;
	int index=1;
	lua_pushinteger(L,watcher);
	lua_gettable(L,-2);
	if(lua_istable(L,-1)==0){
		lua_pop(L,1);
		lua_pushinteger(L,watcher);
		lua_newtable(L);
	}
	else{
		index=luaL_len(L,-1)+1;
		if(index>ud->maxmarker){
			lua_pop(L,1);
			return;
		}	
	}
	lua_pushinteger(L,marker);
	lua_rawseti(L,-2,index);
	if(index==1){
		lua_rawset(L,-3);
	}else{
		lua_pop(L,1);
	}
	/*
	lua_createtable(L,2,0);
	lua_pushinteger(L,watcher);
	lua_rawseti(L,-2,1);
	lua_pushinteger(L,marker);
	lua_rawseti(L,-2,2);
	lua_rawseti(L,-2,index);
	*/
}


static void
init_obj(OBJECT *obj,uint64_t id, float x, float y,float z, float vx, float vy, float vz,const char *mode) {
	obj->id	= id;
	obj->pos[0] =x;
	obj->pos[1] =y;
	obj->pos[2] =z;
	obj->v[0] =vx;
	obj->v[1] =vy;
	obj->v[2] =vz;
	if(mode)strcpy(obj->mode,mode);
}

static void read_obj(lua_State *L,uint64_t index,OBJECT *obj){

	uint64_t  id = luaL_checkinteger(L,index++);
	const char *mode = lua_tostring(L,index++);
	float x=0,y=0,z=0;
	if(mode[0] != 'd'){
		lua_getfield(L,index,"x");
		x = luaL_checknumber(L,-1);
		lua_getfield(L,index,"y");
		y = luaL_checknumber(L,-1);
		lua_getfield(L,index,"z");
		z = luaL_checknumber(L,-1);
	}
	init_obj(obj,id,x,y,z,0,0,0,mode);
}

static struct aoi_space_ud*
checkuserdata(lua_State *L, int index) {
	struct aoi_space_ud * ud = (struct aoi_space_ud*)lua_touserdata(L,index);
	if (ud == NULL || ud->space == NULL) {
		luaL_error(L, "userdata %d is nil",index);
	}
	return ud;
}

static int laoirelease(lua_State *L) {
	struct aoi_space_ud * ud = checkuserdata(L,1);	
	if(ud == NULL){
		return 0;
	}
	if(ud->space!=NULL){
		aoi_release(ud->space);
		ud->space=NULL;
	}
	free(ud);
	return 0;
}

static int laoiupdate(lua_State *L) {
	struct aoi_space_ud * ud = checkuserdata(L,1);	
	OBJECT obj;
	bzero(&obj,sizeof(obj));
	read_obj(L,2,&obj);
	aoi_update(ud->space,obj.id, obj.mode, obj.pos);
	return 0;
}

static int laoimessage(lua_State *L) {
	struct aoi_space_ud * ud = checkuserdata(L,1);	
	lua_newtable(L);
	ud->L=L;
	aoi_message(ud->space,message,ud);
	return 1;
}


static int laoinew(lua_State *L) {
	struct aoi_space * space = aoi_create(my_alloc,&cookie);
	struct aoi_space_ud	*ud	= malloc(sizeof(*ud));
	
	memset(ud,0,sizeof(*ud));

	ud->space=space;
	ud->maxmarker = lua_tointeger(L,1);

	lua_pushlightuserdata(L, ud);

	if (luaL_newmetatable(L, "__spacemeta")) {
		luaL_Reg l[] = {
			{"release",laoirelease},
			{"update",laoiupdate},
			{"message",laoimessage},
			{ NULL, NULL },
		};
		luaL_newlib(L, l);
		lua_setfield(L, -2, "__index");
	}
	lua_setmetatable(L, -2);
	return 1;
}

static int laoimem(lua_State *L){
	lua_newtable(L);
	lua_pushstring(L, "count");
	lua_pushnumber(L, cookie.count);
	lua_settable(L,-3);

	lua_pushstring(L, "max");
	lua_pushnumber(L, cookie.max);
	lua_settable(L,-3);

	lua_pushstring(L, "current");
	lua_pushnumber(L, cookie.current);
	lua_settable(L,-3);
	return 1;
}

int luaopen_aoi_c(lua_State *L) {
	luaL_Reg reg[] = {
		{"aoi_new",laoinew},
		{"aoi_mem",laoimem},
		{NULL,NULL}
	};
	luaL_checkversion(L);
	luaL_newlib(L, reg);
	return 1;
}
